/* * RemoteServerAuth.java * * Copyright (C) 2009-12 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.studio.client.server.remote; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONString; import com.google.gwt.user.client.Random; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.FormPanel; import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent; import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler; import com.google.gwt.user.client.ui.RootPanel; import org.rstudio.core.client.Debug; import org.rstudio.core.client.jsonrpc.RequestLog; import org.rstudio.core.client.jsonrpc.RequestLogEntry; import org.rstudio.core.client.jsonrpc.RequestLogEntry.ResponseType; import org.rstudio.core.client.jsonrpc.RpcError; import org.rstudio.core.client.jsonrpc.RpcResponse; import org.rstudio.studio.client.server.Bool; import org.rstudio.studio.client.server.ServerError; import org.rstudio.studio.client.server.ServerRequestCallback; import java.util.ArrayList; class RemoteServerAuth { public static final int CREDENTIALS_UPDATE_SUCCESS = 1; public static final int CREDENTIALS_UPDATE_FAILURE = 2; public static final int CREDENTIALS_UPDATE_UNSUPPORTED = 3; public RemoteServerAuth(RemoteServer remoteServer) { remoteServer_ = remoteServer; } private Timer periodicUpdateTimer_ = null; public void schedulePeriodicCredentialsUpdate() { // create the callback periodicUpdateTimer_ = new Timer() { @Override public void run() { updateCredentials(new ServerRequestCallback<Integer>() { @Override public void onResponseReceived(Integer response) { switch(response) { case CREDENTIALS_UPDATE_SUCCESS: // do nothing (we just successfully updated our // credentials) break; case CREDENTIALS_UPDATE_FAILURE: // we are not authorized, blow the client away remoteServer_.handleUnauthorizedError(); break; case CREDENTIALS_UPDATE_UNSUPPORTED: // not supported by the back end so cancel the timer periodicUpdateTimer_.cancel(); break; } } @Override public void onError(ServerError serverError) { // if method is not supported then cancel the timer Debug.logError(serverError); } }); } }; // schedule for every 5 minutes final int kMinutes = 5; int milliseconds = kMinutes * 60 * 1000; periodicUpdateTimer_.scheduleRepeating(milliseconds); } public void attemptToUpdateCredentials() { updateCredentials(new ServerRequestCallback<Integer>() { @Override public void onResponseReceived(Integer response) { // this method does nothing in the case of both successfully // updating credentails and method not found. however, if // the credentials update fails then it needs to blow // away the client if (response.intValue() == CREDENTIALS_UPDATE_FAILURE) { remoteServer_.handleUnauthorizedError(); } } @Override public void onError(ServerError serverError) { Debug.logError(serverError); } }); } // save previous form as a precaution against forms which are not // cleaned up due to the submit handler not being called private static ArrayList<FormPanel> previousUpdateCredentialsForms_ = new ArrayList<FormPanel>(); private void safeCleanupPreviousUpdateCredentials() { try { for (int i=0; i<previousUpdateCredentialsForms_.size(); i++) { FormPanel formPanel = previousUpdateCredentialsForms_.get(i); RootPanel.get().remove(formPanel); } previousUpdateCredentialsForms_.clear(); } catch(Throwable e) { } } public void updateCredentials( final ServerRequestCallback<Integer> requestCallback) { // safely cleanup any previously active update credentials forms safeCleanupPreviousUpdateCredentials(); // create a hidden form panel to submit the update credentials to // (we do this so GWT manages the trickiness associated with // managing and reading the contents of a hidden iframe) final FormPanel updateCredentialsForm = new FormPanel(); updateCredentialsForm.setMethod(FormPanel.METHOD_GET); updateCredentialsForm.setEncoding(FormPanel.ENCODING_URLENCODED); // form url String url = remoteServer_.getApplicationURL("auth-update-credentials"); updateCredentialsForm.setAction(url); // request log entry (fake up a json rpc method call to conform // to the data format expected by RequestLog String requestId = Integer.toString(Random.nextInt()); String requestData = createRequestData(); final RequestLogEntry logEntry = RequestLog.log(requestId, requestData); // form submit complete handler updateCredentialsForm.addSubmitCompleteHandler(new SubmitCompleteHandler(){ public void onSubmitComplete(SubmitCompleteEvent event) { // parse the results String results = event.getResults(); RpcResponse response = RpcResponse.parse(event.getResults()); if (response != null) { logEntry.logResponse(ResponseType.Normal, results); // check for error RpcError rpcError = response.getError(); if (rpcError != null) { if (rpcError.getCode() == RpcError.METHOD_NOT_FOUND) { requestCallback.onResponseReceived( new Integer(CREDENTIALS_UPDATE_UNSUPPORTED)); } else { requestCallback.onError(new RemoteServerError(rpcError)); } } else // must be a valid response { Bool authenticated = response.getResult(); if (authenticated.getValue()) { requestCallback.onResponseReceived( new Integer(CREDENTIALS_UPDATE_SUCCESS)); } else { requestCallback.onResponseReceived( new Integer(CREDENTIALS_UPDATE_FAILURE)); } } } else // error parsing results { logEntry.logResponse(ResponseType.Error, results); // form message String msg = "Error parsing results: " + (results != null ? results : "(null)"); // we don't expect this so debug log to flag our attention Debug.log("UPDATE CREDENTIALS: " + msg); // return the error RpcError rpcError = RpcError.create(RpcError.PARSE_ERROR, msg); requestCallback.onError(new RemoteServerError(rpcError)); } // remove the hidden form (from both last-ditch list and DOM) previousUpdateCredentialsForms_.remove(updateCredentialsForm); Scheduler.get().scheduleDeferred(new ScheduledCommand() { public void execute() { RootPanel.get().remove(updateCredentialsForm); } }); } }); // add the (hidden) form panel to the document and last ditch list RootPanel.get().add(updateCredentialsForm, -1000, -1000); previousUpdateCredentialsForms_.add(updateCredentialsForm); // submit the form updateCredentialsForm.submit(); } private String createRequestData() { JSONObject request = new JSONObject() ; request.put("method", new JSONString("update_credentials")); request.put("params", new JSONArray()); return request.toString(); } private final RemoteServer remoteServer_; }